﻿using System;
using System.Xml;
using Notoric.Model.Internal;
using Notoric.Model.Packaging;
using Notoric.Model.Collections;

namespace Notoric.Model.Elements
{
    /// <summary>
    /// A common base class for attachments.
    /// </summary>
    public abstract class AttachmentElement : ModelElement
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="AttachmentElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat that directly owns this element.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// <paramref name="packagePart"/> or <paramref name="owner"/> is <see langword="null"/>.
        /// </exception>
        protected AttachmentElement(CompositionPart packagePart, BeatElement owner)
            : base(packagePart, owner)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            typeAttribute.Value = GetType().FullName;
            voices = new ElementReferenceCollection<VoiceDescriptorElement>(this, voicesElement);
        }

        /// <summary>
        /// Creates a new <see cref="AttachmentElement"/> instance.
        /// </summary>
        /// <param name="packagePart">
        /// <para>
        /// Type: <see cref="CompositionPart"/>
        /// </para>
        /// <para>
        /// The package part that owns this element.
        /// </para>
        /// </param>
        /// <param name="owner">
        /// <para>
        /// Type: <see cref="BeatElement"/>
        /// </para>
        /// <para>
        /// The beat that directly owns this element.
        /// </para>
        /// </param>
        /// <param name="element">
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The XML element that holds the data of this object.
        /// </para>
        /// </param>
        /// <exception cref="ArgumentNullException">
        /// Any of the arguments is <see langword="null"/>.
        /// </exception>
        protected AttachmentElement(CompositionPart packagePart, BeatElement owner, XmlElement element)
            : base(packagePart, owner, element)
        {
            if (owner == null)
                throw new ArgumentNullException("owner");

            typeAttribute = element.Attributes[PackageVocabulary.TypeAttribute];
            voicesElement = element[PackageVocabulary.VoiceDescriptorElementName + "s", PackageVocabulary.PackageNamespaceUri];

            voices = new ElementReferenceCollection<VoiceDescriptorElement>(this, voicesElement);
        }

        #endregion

        #region ModelElement Members

        /// <inheritdoc/>
        protected override XmlElement CreateObjectElement(XmlDocument document)
        {
            var element = CreateObjectElementCore(document);

            voicesElement = document.CreateElement(
                PackageVocabulary.PackageNamespacePrefix,
                PackageVocabulary.VoiceDescriptorElementName + "s",
                PackageVocabulary.PackageNamespaceUri
                );

            typeAttribute = document.CreateAttribute(PackageVocabulary.TypeAttribute);

            element.Attributes.Append(typeAttribute);

            element.AppendChild(voicesElement);

            return element;
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// When overriden in a derived class, this method creates the XML element
        /// that will hold serialized data of this object.
        /// </summary>
        /// <remarks>
        /// The attributes for the inherited data fields as well as the type information
        /// will be added by the base class.
        /// </remarks>
        /// <param name="document">
        /// <para>
        /// Type: <see cref="XmlDocument"/>
        /// </para>
        /// <para>
        /// The XML document that will containt the created element.
        /// </para>
        /// </param>
        /// <returns>
        /// <para>
        /// Type: <see cref="XmlElement"/>
        /// </para>
        /// <para>
        /// The created XML element.
        /// </para>
        /// </returns>
        protected abstract XmlElement CreateObjectElementCore(XmlDocument document);

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets the collection of voices that this attachment applies to.
        /// </summary>
        /// <value>
        /// <para>
        /// Type: <see cref="ElementReferenceCollection{T}"/> of <see cref="VoiceDescriptorElement"/>
        /// </para>
        /// <para>
        /// If the attachment applies only to concrete voices, this collection contains
        /// their descriptors. An empty collection is interpreted as an attachment
        /// that applies to all voices in the part.
        /// </para>
        /// </value>
        public ElementReferenceCollection<VoiceDescriptorElement> Voices
        {
            get { return voices; }
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the type of the attachment class.
        /// </summary>
        private XmlAttribute typeAttribute;
        /// <summary>
        /// Holds the value of the <see cref="Voices"/> property.
        /// </summary>
        private ElementReferenceCollection<VoiceDescriptorElement> voices;
        /// <summary>
        /// Holds the XML element that holds the <see cref="Voices"/> collection.
        /// </summary>
        private XmlElement voicesElement;

        #endregion
    }
}
